/**
 * @file opdtest.c
 *
 * @brief Problem solver.
 *
 * @date 1998 - 2010
 * @author Richard Delorme
 * @version 4.0
 */
#include "search.h"
#include "options.h"
#include "const.h"


/** OPD structure: Othello Position Descrition */
typedef struct OPD {
	Board board[1];
	int player;
	struct {
		int x;
		int score;
	} move[MAX_MOVE];
	int n_moves;
	int best_score;
	char *comments;
} OPD;

enum {
	OPD_PARSE_OK,
	OPD_PARSE_SKIP,
	OPD_PARSE_END
};

void opd_free(OPD *opd)
{
	free(opd->comments);
	opd->comments = NULL;
}

static void opd_write(OPD *opd, FILE *f)
{
	char s[80];
	int i;

	if ((opd->board->player | opd->board->opponent) != 0) {
		board_to_string(opd->board, opd->player, s);
		fprintf(f, "%s;", s);
		for (i = 0; i < opd->n_moves; ++i) {
			putc(' ', f);
			move_print(opd->move[i].x, 0, f);
			fprintf(f, ":%+d;", opd->move[i].score);
		}
		if (i == 0) fprintf(f, " %+d;", opd->best_score);

	}
	if (opd->comments) printf(" %% %s", opd->comments);
	putc('\n', f);
	fflush(f);
}

static int opd_read(OPD *opd, FILE *f)
{
	char *string, *line, *next;
	char name[4];
	Move move[1];
	int parse_ok;

	opd->n_moves = 0;
	opd->best_score = -SCORE_INF;
	opd->board->player = opd->board->opponent = 0;
	opd->comments = NULL;

	line = string_read_line(f);
	if (line) {

		string = parse_skip_spaces(line);
		if (*string == '%') {
			opd->comments = string_duplicate(string + 1);
			parse_ok = OPD_PARSE_SKIP;
		} else if (*string == '\n' || *string == '\r' || *string == '\0') {
			parse_ok = OPD_PARSE_SKIP;
		} else {
			next = parse_board(string, opd->board, &opd->player);
			parse_ok = (next > string) ? OPD_PARSE_OK : OPD_PARSE_SKIP;
		}

		while (parse_ok == OPD_PARSE_OK && *(string = parse_find(next, ';')) == ';') {
			next = parse_move(++string, opd->board, move);
			if (next != string) {
				string = parse_find(next, ':');
				if (*string == ':') ++string;
				else {
					warn("missing score in %s (%d) %s %s\n", line, move->x, next, string);
					printf("read>"); opd_write(opd, stdout);
					parse_ok = OPD_PARSE_SKIP;
					break;
				}
			}
			move->score = -SCORE_INF;
			next = parse_int(string, &move->score);
			if (next == string && opd->best_score == -SCORE_INF) {
				warn("missing best score in %s\n", line);
				break;
			}

			if (move->score > opd->best_score) opd->best_score = move->score;
			opd->move[opd->n_moves].x = move->x;
			opd->move[opd->n_moves].score = move->score;
			++opd->n_moves;
		}

		free(line);
		return parse_ok;
	}

	return OPD_PARSE_END;
}

static void opd_search(Search *search, OPD *opd, int n)
{
	int i, j;

	search_cleanup(search);
	search_set_board(search, opd->board, opd->player);
	search_set_level(search, options.level, search->n_empties);
	if (options.depth >= 0) search->options.depth = MIN(options.depth, search->n_empties);
	if (options.selectivity >= 0) search->options.selectivity = options.selectivity;

	if (options.play_type == EDAX_TIME_PER_MOVE) search_set_move_time(search, options.time);
	else search_set_game_time(search, options.time);

	if (options.verbosity >= 2) {
		printf("\n*** problem # %d ***\n\n", n);
		board_print(search->board, search->player, stdout);
		putchar('\n');
		puts(search->options.header);
		puts(search->options.separator);
	} else if (options.verbosity == 1) printf("%3d|", n);

	search_run(search);

	if (options.verbosity) {
		if (options.verbosity == 1) { 
			result_print(search->result, stdout);
			putchar(' ');
		}
		for (i = 0; i < opd->n_moves; ++i) {
			if (opd->move[i].x == search->result->move) break;
		}
		if (i < opd->n_moves) {
			if (opd->move[i].score != opd->best_score) {
				printf("Erroneous move: ");
				for (j = 0; j < opd->n_moves; ++j) {
					if (opd->move[j].score == opd->best_score) {
						move_print(opd->move[j].x, opd->player, stdout);
						putchar(' ');
					}
				}
				printf("expected, with score %+d, error = %+d", opd->best_score, opd->best_score - opd->move[i].score);
			}
		} else if (opd->best_score != search->result->score) {
			printf("Erroneous score: %+d expected", opd->best_score);
		}
		putchar('\n');
		if (options.verbosity >= 2) {
			puts(search->options.separator);
		}
		fflush(stdout);
	}
}

static void opd_build(Search *search, OPD *opd, int n)
{
	int n_moves;
	
	search_cleanup(search);
	search_set_board(search, opd->board, opd->player);
	search_set_level(search, options.level, search->n_empties);
	if (options.depth >= 0) search->options.depth = MAX(options.depth, search->n_empties);
	if (options.selectivity >= 0) search->options.selectivity = options.selectivity;

	if (options.play_type == EDAX_TIME_PER_MOVE) search_set_move_time(search, options.time);
	else search_set_game_time(search, options.time);


	if (options.verbosity >= 2) {
		printf("\n*** problem # %d ***\n\n", n);
		if (opd->comments) printf("* %s *\n\n", opd->comments);
		board_print(search->board, search->player, stdout);
		putchar('\n');
		puts(search->options.header);
		puts(search->options.separator);
		puts(search->options.separator);
	}

	opd->n_moves = 0;
	opd->best_score = -SCORE_INF;
	search->result->score = -SCORE_INF;
	n_moves = search->movelist->n_moves;

	if (n_moves == 0) {
		if (options.verbosity == 1) printf("%3d|", n);
		search_run(search);
		if (opd->best_score < search->result->score) opd->best_score = search->result->score;
		if (search->result->move == PASS) {
			opd->move[opd->n_moves].x = search->result->move;
			opd->move[opd->n_moves].score = search->result->score;
			++opd->n_moves;
		}
	}

	while (n_moves--) {
		if (options.verbosity == 1) printf("%3d|", n);

			search_run(search);

		opd->move[opd->n_moves].x = search->result->move;
		opd->move[opd->n_moves].score = search->result->score;
		if (opd->best_score < search->result->score) opd->best_score = search->result->score;
		++opd->n_moves;

		hash_exclude_move(search->pv_table, board_get_hash_code(search->board), search->result->move);
		hash_exclude_move(search->hash_table, board_get_hash_code(search->board), search->result->move);
		movelist_exclude(search->movelist, search->result->move);
	}

	if (options.verbosity) {
		puts(search->options.separator);
		if (options.verbosity >= 2) putchar('\n');
		fflush(stdout);
	}
}


/** opdtest */
void opd_test(Search *search, const char *file_name)
{
	FILE *f;
	OPD opd[1];
	unsigned long long T = 0, n_nodes = 0;
	int n = 0, n_bad_score = 0, n_bad_move = 0;
	double score_error = 0.0, move_error = 0.0;
	int i, ok;

	// add observers
	search_set_observer(search, search_observer);
	search->options.verbosity = (options.verbosity == 1 ? 0 : options.verbosity);

	// open script file with problems
	if (file_name == NULL) usage();
	f = fopen(file_name, "r");
	if (f == NULL) {
		fprintf(stderr, "solver: cannot open script file %s\n", file_name);
		exit(EXIT_FAILURE);
	}
	
	if (options.verbosity == 1) {
		if (search->options.header) printf(" # |%s\n", search->options.header);
		if (search->options.separator) printf("---+%s\n", search->options.separator);
	}

	while ((ok = opd_read(opd, f)) != OPD_PARSE_END) {
		if (ok == OPD_PARSE_OK) {
			opd_search(search, opd, ++n);
		
			T += search_time(search);
			n_nodes += search_count_nodes(search);
			for (i = 0; i < opd->n_moves; ++i) {
				if (opd->move[i].x == search->result->move) break;
			}
			if (i < opd->n_moves) {
				if (opd->move[i].score < opd->best_score) ++n_bad_move;
				if (opd->move[i].score != search->result->score) ++n_bad_score;
				move_error += abs(opd->best_score - opd->move[i].score);
			}
			score_error += abs(opd->best_score - search->result->score);
		}
		opd_free(opd);			
	}

	if (options.verbosity == 1 && search->options.separator) puts(search->options.separator);
	printf("%.30s : ", file_name);
	if (n_nodes) printf("%llu nodes in ", n_nodes);
	time_print(T, stdout);
	if (T > 0 && n_nodes > 0) printf(" (%8.0f nodes/s).", 1000.0 * n_nodes / T);
	printf("\n");
	
	printf("%d positions; ", n);
	printf("%d erroneous move; ", n_bad_move);
	printf("%d erroneous score; ", n_bad_score);
	printf("mean absolute score error = %.3f; ", score_error / n);
	printf("mean absolute move error = %.3f\n", move_error / n);

	fclose(f);
}

void script_to_opd(Search *search, const char *script_file, const char *opd_file)
{
	
	FILE *i, *o;
	OPD opd[1];
	int n = 0, ok;

	// add observers
	search_set_observer(search, search_observer);
	search->options.verbosity = options.verbosity;

	// open script file with problems
	if (script_file == NULL || opd_file == NULL) {
		warn("script_to_opd: missing files\n");
		return;
	}
	if (strcmp(script_file, opd_file) == 0) {
		warn("script_to_opd: files should be different\n");
		return;
	}

	i = fopen(script_file, "r");
	if (i == NULL) {
		warn("script_to_opd: cannot open script file %s\n", script_file);
		return;
	}
	o = fopen(opd_file, "w");
	if (o == NULL) {
		warn("script_to_opd: cannot open opd file %s\n", opd_file);
		fclose(i);
		return;
	}
	
	if (options.verbosity == 1) {
		if (search->options.header) printf(" # |%s\n", search->options.header);
		if (search->options.separator) printf("---+%s\n", search->options.separator);
	}

	while ((ok = opd_read(opd, i)) != OPD_PARSE_END) {
		if (ok == OPD_PARSE_OK) {
			opd_build(search, opd, ++n);
		}
		opd_write(opd, o);
		opd_free(opd);			
	}

	if (options.verbosity == 1 && search->options.separator) puts(search->options.separator);
	putchar('\n');

	fclose(o);
	fclose(i);

}

